package LDraw.Support;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeSet;
import Command.LDrawColor;
import Command.LDrawColorT;
import Command.LDrawLSynth;
import Command.LDrawLine;
import Command.LDrawPart;
import Command.LDrawQuadrilateral;
import Command.LDrawTexture;
import Command.LDrawTriangle;
import Command.PartTypeT;
import Common.Box3;
import Common.Vector3f;
import LDraw.Files.LDrawModel;
import LDraw.Files.LDrawStep;
import LDraw.Support.type.ViewOrientationT;
public class LDrawUtilities {
private static LDrawVertices boundingCube = null;
private static boolean ColumnizesOutput = false;
private static String defaultAuthor = "anonymous";
// ---------- angleForViewOrientation:
// --------------------------------[static]--
//
// Purpose: Returns the viewing angle in degrees for the given orientation.
//
// ------------------------------------------------------------------------------
public static Vector3f angleForViewOrientation(ViewOrientationT orientation) {
Vector3f angle = Vector3f.getZeroVector3f();
switch (orientation) {
case ViewOrientationWalkThrough:
angle = MatrixMath.V3Make(0, 0, 0);
break;
case ViewOrientation3D:
// This is MLCad's default 3-D viewing angle, which is arrived at by
// applying these rotations in order: z=0, y=45, x=23.
angle = MatrixMath.V3Make(30.976f, 40.609f, 21.342f);
break;
case ViewOrientationFront:
angle = MatrixMath.V3Make(0, 0, 0);
break;
case ViewOrientationBack:
angle = MatrixMath.V3Make(0, 180, 0);
break;
case ViewOrientationLeft:
angle = MatrixMath.V3Make(0, -90, 0);
break;
case ViewOrientationRight:
angle = MatrixMath.V3Make(0, 90, 0);
break;
case ViewOrientationTop:
angle = MatrixMath.V3Make(90, 0, 0);
break;
case ViewOrientationBottom:
angle = MatrixMath.V3Make(-90, 0, 0);
break;
}
return angle;
}
// ---------- stringFromFile:
// -----------------------------------------[static]--
//
// Purpose: Reads the contents of the file at the given path into a string.
//
// ------------------------------------------------------------------------------
@SuppressWarnings("resource")
public static String stringFromFile(String path) {
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new FileReader(new File(path)));
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null)
sb.append(line + "\n");
return sb.toString();
} catch (Exception e) {
}
return null;
}// end stringFromFile:
public static Box3 boundingBox3ForDirectives(
ArrayList<LDrawDirective> directives) {
Box3 bounds = Box3.getInvalidBox();
Box3 partBounds = Box3.getInvalidBox();
LDrawDirective currentDirective = null;
int numberOfDirectives = directives.size();
int counter = 0;
for (counter = 0; counter < numberOfDirectives; counter++) {
currentDirective = directives.get(counter);
{
partBounds = currentDirective.boundingBox3();
bounds = MatrixMath.V3UnionBox(bounds, partBounds);
}
}
return bounds;
}
public static String defaultAuthor() {
// TODO Auto-generated method stub
return "anonymous";
}
// ---------- readNextField:remainder:
// --------------------------------[static]--
//
// Purpose: Given the portion of the LDraw line, read the first available
// field. Fields are separated by whitespace of any length.
//
// If remainder is not NULL, return by indirection the remainder of
// partialDirective after the first field has been removed. If
// there is no remainder, an empty string will be returned.
//
// So, given the line
// 1 8 -150 -8 20 0 0 -1 0 1 0 1 0 0 3710.DAT
//
// remainder will be set to:
// 8 -150 -8 20 0 0 -1 0 1 0 1 0 0 3710.DAT
//
// Notes: This method is incapable of reading field strings with spaces
// in them!
//
// A case could be made to replace this method with an NSScanner!
// They don't seem to be as adept at scanning in unknown string
// tags though, which would make them difficult to use to
// distinguish between "0 WRITE blah" and "0 COMMENT blah".
//
// ------------------------------------------------------------------------------
// public static String readNextField(ByteBuffer workingLine) {
// String partialDirective;
// int position = workingLine.position();
// byte[] strTemp = new byte[workingLine.remaining()];
// workingLine.get(strTemp);
// partialDirective = new String(strTemp);
// workingLine.position(position);
//
// Range rangeOfNextWhiteSpace;
// String fieldContents = null;
//
// // First, remove any heading whitespace.
// partialDirective = partialDirective.trim();
// // Find the beginning of the next field separation
// rangeOfNextWhiteSpace = new Range(partialDirective.indexOf(" "),
// partialDirective.length());
//
// // The text between the beginning and the next field separator is the
// // first field (what we are after).
// if (rangeOfNextWhiteSpace.getLocation() != -1) {
// fieldContents = partialDirective.substring(0,
// rangeOfNextWhiteSpace.getLocation());
// // See if they want the rest of the line, sans the field we just
// // parsed.
// if (workingLine != null) {
// workingLine.position(position+rangeOfNextWhiteSpace.getLocation()+1);
// }
//
// } else {
// // There was no subsequent field separator; we must be at the end of
// // the line.
// fieldContents = partialDirective;
// if (workingLine != null) {
// workingLine.position(workingLine.capacity());
// // while (workingLine.remaining() > 0)
// // workingLine.get();
// }
// }
//
// return fieldContents;
//
// }
public static boolean isLDrawFilenameValid(String acceptableName) {
boolean isValid = false;
if (acceptableName == null)
return false;
int indexOfDot = acceptableName.lastIndexOf(".");
if (indexOfDot == -1)
return false;
if (indexOfDot == 0)
return false;
String ext = acceptableName.substring(indexOfDot + 1,
acceptableName.length()).toLowerCase();
if (ext.equals("ldr"))
isValid = true;
if (ext.equals("dat"))
isValid = true;
return isValid;
}
// ---------- classForDirectiveBeginningWithLine:
// ---------------------[static]--
//
// Purpose: Allows initializing the right kind of class based on the code
// found at the beginning of an LDraw line.
//
// ------------------------------------------------------------------------------
public static LDrawDirective classForDirectiveBeginningWithLine(String line) {
LDrawDirective classForType = null;
String[] commandCodeStrings = null;
char lineType = 0;
// commandCodeString = LDrawUtilities.readNextField(ByteBuffer.wrap(line
// .getBytes()));
// commandCodeString = new StringTokenizer(line).nextToken(" ");
commandCodeStrings = line.split("\\s+");
for (String code : commandCodeStrings) {
if (!"".equals(code)) {
lineType = code.charAt(0);
break;
}
}
// We may need to check for null here someday.
// The linecode (0, 1, 2, 3, 4, 5) identifies the type of command, and
// is
// always the first character in the line.
switch (lineType) {
case '0': {
if (LDrawTexture.lineIsTextureBeginning(line))
classForType = new LDrawTexture();
else if (LDrawLSynth.lineIsLSynthBeginning(line)) {
classForType = new LDrawLSynth();
} else
classForType = new LDrawMetaCommand();
}
break;
case '1':
classForType = new LDrawPart();
break;
case '2':
classForType = new LDrawLine();
break;
case '3':
classForType = new LDrawTriangle();
break;
case '4':
classForType = new LDrawQuadrilateral();
break;
case '5':
classForType = new LDrawConditionalLine();
break;
default:
System.out.println(String.format(
"unrecognized LDraw line type: %s", lineType));
}
return classForType;
}
// ---------- parseColorFromField:
// ------------------------------------[static]--
//
// Purpose: Returns the color code which is represented by the field.
//
// Notes: This supports a nonstandard but fairly widely-supported
// extension which allows arbitrary RGB values to be specified in
// place of color codes. (MLCad, L3P, LDView, and others support
// this.)
//
// ------------------------------------------------------------------------------
public static LDrawColor parseColorFromField(String colorField) {
LDrawColorT colorCode = LDrawColorT.LDrawColorBogus;
int customCodeType = 0;
float components[] = new float[4];
LDrawColor color = null;
// Custom RGB?
if (colorField.contains("0x") == true) {
// The integer should be of the format:
// 0x2RRGGBB for opaque colors
// 0x3RRGGBB for transparent colors
// 0x4RGBRGB for a dither of two 12-bit RGB colors
// 0x5RGBxxx as a dither of one 12-bit RGB color with clear (for
// transparency).
customCodeType = Integer.parseInt("" + colorField.charAt(2));
String hexStr = colorField.substring(3, 9);
LDrawColor.scanHexString(hexStr, components);
// todo
switch (customCodeType) {
// Solid color
case 2:
// components[0] = Integer.parseInt("" + hexBytes[2], 16) /
// 255.0f; // Red
// components[1] = Integer.parseInt("" + hexBytes[1], 16) /
// 255.0f; // Green
// components[2] = Integer.parseInt("" + hexBytes[0], 16) /
// 255.0f; // Blue
components[3] = (float) 1.0; // alpha
break;
// Transparent color
case 3:
// components[0] = Integer.parseInt("" + hexBytes[2], 16) /
// 255.0f; // Red
// components[1] = Integer.parseInt("" + hexBytes[1], 16) /
// 255.0f; // Green
// components[2] = Integer.parseInt("" + hexBytes[0], 16) /
// 255.0f; // Blue
components[3] = (float) 0.5; // alpha
break;
// combined opaque color
case 4:
// components[0] = (float) (((hexBytes >> 5*4) & 0xF) +
// ((hexBytes >> 2*4) & 0xF))/2 / 255; // Red
// components[0] = (float) (((hexBytes >> 4*4) & 0xF) +
// ((hexBytes >> 1*4) & 0xF))/2 / 255; // Green
// components[0] = (float) (((hexBytes >> 3*4) & 0xF) +
// ((hexBytes >> 0*4) & 0xF))/2 / 255; // Blue
components[3] = (float) 1.0; // alpha
break;
// bad-looking transparent color
case 5:
// components[0] = (float) ((hexBytes >> 5*4) & 0xF) / 15; //
// Red
// components[0] = (float) ((hexBytes >> 4*4) & 0xF) / 15; //
// Green
// components[0] = (float) ((hexBytes >> 3*4) & 0xF) / 15; //
// Blue
components[3] = (float) 0.5; // alpha
break;
default:
break;
}
color = new LDrawColor();
color.setColorCode(LDrawColorT.LDrawColorCustomRGB);
color.setEdgeColorCode(LDrawColorT.LDrawBlack);
color.setColorRGBA(components);
} else {
// Regular, standards-compliant LDraw color code
int colorCodeValue = Integer.parseInt(colorField);
for (LDrawColorT colorT : LDrawColorT.values())
if (colorT.getValue() == colorCodeValue) {
colorCode = colorT;
break;
}
color = ColorLibrary.sharedColorLibrary().colorForCode(colorCode);
if (color == null) {
// This is probably a file-local color. Or a file from the
// future.
color = new LDrawColor();
color.setColorCode(colorCode);
color.setEdgeColorCode(LDrawColorT.LDrawBlack);
}
}
return color;
}
public static String outputStringForColor(LDrawColor color) {
String outputString = null;
float components[] = new float[4];
LDrawColorT colorCode = LDrawColorT.LDrawColorBogus;
colorCode = color.colorCode();
color.getColorRGBA(components);
if (colorCode == LDrawColorT.LDrawColorCustomRGB) {
// Opaque?
if (components[3] == 1.0f) {
outputString = String.format("0x2%02X%02X%02X",
(components[0] * 255), (components[1] * 255),
(components[2] * 255));
} else {
outputString = String.format("0x3%02X%02X%02X",
(components[0] * 255), (components[1] * 255),
(components[2] * 255));
}
} else {
if (ColumnizesOutput == true) {
outputString = String.format("%3d", colorCode.getValue());
} else {
outputString = String.format("%d", colorCode.getValue());
}
}
return outputString;
}
public static String outputStringForFloat(float f) {
return String.format("%.3f",
Math.round(f / LDrawGlobalFlag.DecimalPoint)
* LDrawGlobalFlag.DecimalPoint);
}
// ---------- boundingCube
// --------------------------------------------[static]--
//
// Purpose: Returns a drawable unit cube which may be scaled to render
// bounding boxes using optimized OpenGL code.
//
// ------------------------------------------------------------------------------
public static LDrawVertices boundingCube() {
if (boundingCube == null) {
// Create it for the first time.
// It's easiest to co-opt existing LDraw objects for this.
boundingCube = new LDrawVertices();
LDrawColor currentColor = ColorLibrary.sharedColorLibrary()
.colorForCode(LDrawColorT.LDrawCurrentColor);
Vector3f vertices[] = { MatrixMath.V3Make(0, 0, 0),
MatrixMath.V3Make(0, 0, 1), MatrixMath.V3Make(0, 1, 1),
MatrixMath.V3Make(0, 1, 0),
MatrixMath.V3Make(1, 0, 0), MatrixMath.V3Make(1, 0, 1),
MatrixMath.V3Make(1, 1, 1), MatrixMath.V3Make(1, 1, 0), };
LDrawQuadrilateral side0 = new LDrawQuadrilateral();
LDrawQuadrilateral side1 = new LDrawQuadrilateral();
LDrawQuadrilateral side2 = new LDrawQuadrilateral();
LDrawQuadrilateral side3 = new LDrawQuadrilateral();
LDrawQuadrilateral side4 = new LDrawQuadrilateral();
LDrawQuadrilateral side5 = new LDrawQuadrilateral();
side0.setLDrawColor(currentColor);
side1.setLDrawColor(currentColor);
side2.setLDrawColor(currentColor);
side3.setLDrawColor(currentColor);
side4.setLDrawColor(currentColor);
side5.setLDrawColor(currentColor);
side0.setVertex1(vertices[0]);
side0.setVertex2(vertices[3]);
side0.setVertex3(vertices[2]);
side0.setVertex4(vertices[1]);
side1.setVertex1(vertices[0]);
side1.setVertex2(vertices[4]);
side1.setVertex3(vertices[7]);
side1.setVertex4(vertices[3]);
side2.setVertex1(vertices[3]);
side2.setVertex2(vertices[7]);
side2.setVertex3(vertices[6]);
side2.setVertex4(vertices[2]);
side3.setVertex1(vertices[2]);
side3.setVertex2(vertices[6]);
side3.setVertex3(vertices[5]);
side3.setVertex4(vertices[1]);
side4.setVertex1(vertices[1]);
side4.setVertex2(vertices[5]);
side4.setVertex3(vertices[4]);
side4.setVertex4(vertices[0]);
side5.setVertex1(vertices[4]);
side5.setVertex2(vertices[5]);
side5.setVertex3(vertices[6]);
side5.setVertex4(vertices[7]);
boundingCube.addQuadrilateral(side0);
boundingCube.addQuadrilateral(side1);
boundingCube.addQuadrilateral(side2);
boundingCube.addQuadrilateral(side3);
boundingCube.addQuadrilateral(side4);
boundingCube.addQuadrilateral(side5);
}
return boundingCube;
}
public static void registerHitForObject(LDrawDirective hitObject,
FloatBuffer hitDepth, LDrawDirective creditObject,
HashMap<LDrawDirective, Float> hits) {
Float existingRecord = null;
if (hits.containsKey(creditObject))
existingRecord = hits.get(creditObject);
float existingDepth = 0;
LDrawDirective key = null;
// NSDictionary copies its keys (which we don't want to do!), so we'll
// just
// wrap the pointers.
if (creditObject == null) {
key = hitObject;
} else {
key = creditObject;
}
if (hits.containsKey(key))
existingRecord = hits.get(key);
if (existingRecord == null) {
existingDepth = Float.POSITIVE_INFINITY;
} else {
existingDepth = existingRecord.floatValue();
}
// Found a shallower intersection point? Record the hit.
if (hitDepth.get(0) < existingDepth) {
hits.put(key, new Float(hitDepth.get(0)));
}
}
public static void registerHitForObject(LDrawDirective hitObject,
LDrawDirective creditObject, TreeSet<LDrawDirective> hits) {
LDrawDirective key = null;
if (creditObject == null) {
key = hitObject;
} else {
key = creditObject;
}
hits.add(key);
}
public static String excludeExtensionFromPartName(String partName) {
if (partName == null)
return partName;
int lastIndexOfDot = partName.lastIndexOf(".");
if (lastIndexOfDot == -1)
return partName;
else
return partName.substring(0, lastIndexOfDot);
}
public static String excludePatternWithoutExtension(String partName) {
String result = "";
for (int i = 0; i < partName.length(); i++) {
if (Character.isDigit(partName.charAt(i)))
continue;
if (partName.charAt(i) == 'a' || partName.charAt(i) == 'b')
continue;
result = partName.substring(0, i);
break;
}
return result;
}
public static String excludePattern(String partName) {
String result = "";
for (int i = 0; i < partName.length(); i++) {
if (Character.isDigit(partName.charAt(i)))
continue;
if (partName.charAt(i) == 'a' || partName.charAt(i) == 'b')
continue;
result = partName.substring(0, i) + ".dat";
break;
}
return result;
}
public static ArrayList<LDrawPart> extractLDrawPartListModel(
LDrawModel ldrModel, boolean extractMPD) {
if (ldrModel == null)
return null;
ArrayList<LDrawPart> retList = new ArrayList<LDrawPart>();
for (LDrawStep step : ldrModel.steps()) {
for (LDrawDirective directive : step.subdirectives()) {
if (directive instanceof LDrawPart) {
LDrawPart part = (LDrawPart) directive;
if (extractMPD == true
&& part.getCacheType() == PartTypeT.PartTypeSubmodel) {
ArrayList<LDrawPart> tempList = extractLDrawPartListModel(
part.getCacheModel(), extractMPD);
if (tempList != null)
retList.addAll(tempList);
} else
retList.add(part);
}
}
}
return retList;
}
public static boolean isIntersected(Box3 box1, Box3 box2) {
Vector3f max_box1 = box1.getMax();
Vector3f min_box1 = box1.getMin();
Vector3f max_box2 = box2.getMax();
Vector3f min_box2 = box2.getMin();
if (min_box1.y >= max_box2.y || max_box1.y <= min_box2.y)
return false;
if (max_box1.x - 1 <= min_box2.x || max_box1.z - 1 <= min_box2.z)
return false;
if (min_box1.x >= max_box2.x - 1 || min_box1.z >= max_box2.z - 1)
return false;
// System.out.println("####################");
// System.out.println("max_box1: "+max_box1+", min_box1: "+min_box1);
// System.out.println("max_box2: "+max_box2+", min_box1: "+min_box2);
return true;
}
}